/**
 * @file
 * @date 2020-10-14
 * @brief enums, Upgrade and Product classes
 */

#pragma once

#include <string>
#include <array>

constexpr double GENERATOR = 0.05;                          ///< generator base multiplier
constexpr double EFFICIENCY = 0.1;                          ///< efficiency base multiplier
constexpr int MAXLEVEL = 9999;                              ///< max upgrade level
constexpr int MAXLEVELREDUCTION = 75;                       ///< max upgrade level for cost reduction

/*! All possible product types */
enum class ProductType : int
{
    Power,                                                  ///< Power for buy orbs
    XP,                                                     ///< XP for buying researches
    Asset,                                                  ///< Assets for buy buildings
};

/*! All possible upgrade types */
enum class UpgradeType : int
{
    SolarPanel,                                             ///< Increase Solar Plant level
    WindMill,                                               ///< Increase Wind Mill level
    Reflector,                                              ///< Increase Reflector level
    Generator_Power,                                        ///< Increase Power generation
    Generator_XP,                                           ///< Increase XP generation
    Generator_Asset,                                        ///< Increase Assets generation
    Effeciency_SolarPanel,                                  ///< Increase Solar Plant production
    Effeciency_WindWill,                                    ///< Increase Wind Mill production
    Effeciency_Reflector,                                   ///< Increase Reflector production
    Cost_Reduction_Assets,                                  ///< Decrease level up cost for SolarPanel  WindMill  Reflector
    Cost_Reduction_Orb                                      ///< Decrease orb cost
};

const std::string upgradeNames[] = {
    "Solar Panel",
    "Wind Mill",
    "Reflector",
    "Power Generator",
    "XP Generator",
    "Asset Generator",
    "Solar Efficiency",
    "Wind Mill Efficiency",
    "Reflector Efficiency",
    "Cost reduction",
    "Orb cost reduction"
};

/**
 * @class Upgrade
 * @brief Class for describe upgrade entity
 * 
 * - Every upgrade have name, next level cost and effect
 * - Upgrading cost calculate by formula: P(n) = B * n * n, where n is upgrade level and B is basic cost
 * - Basic cost for all researches is 200
 * - Basic cost for Solar Plant is 50, for Wind Mill is 200 and for Reflector - 1000
 * @note first upgrade for Reflector always cost 1000
 */
class Upgrade
{
    const UpgradeType type;
    const std::string name;
    const int64_t price;
    int64_t level;
    double priority = 0;

public:
    /**
     * @brief Construct Upgrade object
     * @param   aType   upgrade type, @sa ::UpgradeType
     * @param   aName   upgrade name
     * @param   aPrice  base price
     * @param   aLevel  initial level
     */
    explicit Upgrade( UpgradeType aType, const std::string & aName, int64_t aPrice, int64_t aLevel = 0 );

    /**
     * @brief Copy constructor
     * @param   other   copy from
     */
    explicit Upgrade( const Upgrade & other );

    /**
     * @brief Copy assignment
     * @param   other   Copy from
     */
    Upgrade & operator=( const Upgrade & other ) noexcept;

    /**
     * @brief Price diff between first and last level
     * @param   price   basic price
     * @param   first   from level
     * @param   last    to level
     * @return sum different between two levels
     * 
     * @note
     * - @f$price(level)=basePrice*(level^2)@f$
     * - @f$\sum\limits_{level=1}^n price(level) =  \frac{ basePrice *   level*(level+1)*(2*level+1)  }{6} @f$
     */
    static double diffPrice( int64_t price, int64_t first, int64_t last );

    /**
     * @brief Calculate upgrade price for next @p n levels
     * @param n number of levels to buy
     * @return upgrade price for next @p n levels
     */
    double upgradePrice( int64_t n = 1 ) const;

    /**
     * @brief Calculate upgrade price from @p begin to @p end level
     * @param   begin   begin level
     * @param   end     end level
     */
    double upgradePrice( int64_t begin, int64_t end ) const;

    /**
     * @brief Change upgrade level
     * @param   aLevel      level value
     * @param   overwrite   overwrite on true, increase on false
     * @return new upgrade level
     */
    int64_t upgrade( int64_t aLevel, bool overwrite = false );

    /**
     * @brief Get current level
	 * @return current level
     */
    int64_t getLevel() const;

    /**
     * @brief Get upgrade name
     * @return upgrade name
     */
    const std::string & getName() const;
    
    /**
     * @brief Get upgrade name by type
     * @param type upgrade type
     * @return upgrade name
     */
    static const std::string & getNameByType(UpgradeType type);

    /**
     * @brief Get upgrade type
     * @return upgrade type
     */
    UpgradeType getType() const;

    /**
     * @brief Get basic price
     * @return current price
     */
    int64_t getPrice() const;

    /**
     * @brief Get current priority
     * @return prority value
     */
    double getPriority() const;

    /**
     * @brief change priority
     * @param aPriority new priority value
     */
    void setPriority( double aPriority );
};

/**
 * @class Product
 * @brief Class for describe product
 */
struct Product
{
    const ProductType type;                                 ///< product type
    const std::string name;                                 ///< product name
    double value = 0;                                       ///< current product value

    /**
     * @brief product weight
     * @note more weight = more priority for product generator
     */
    double weight = 1;

    /**
     * @brief product factor
     * @note this field used for correcting weights or priorities
     */
    double factor = 1;

    /**
     * @brief Construct Product object
     * @param   aType   product type
     * @param   aName   product name
     */
    explicit Product( ProductType aType, const std::string & aName );

    /**
     * @brief Copy constructor
     * @param   other   copy from
     */
    explicit Product( const Product & other );

    /**
     * @brief copy assignment
     * @param   other   Copy from
     */
    Product & operator=( const Product & other ) noexcept;
};

/**
 * @brief calc pow of 10
 * @param   n   exponent
 */
constexpr int64_t pow10( int n ) {
    int64_t result = 1;
    while ( n-- )
    {
        result *= 10;
    }
    return result;
}

/**
 * @struct PowerOrb
 * @brief Struct for describe power orbs
 */
struct PowerOrb
{
    static const int64_t maxPower = 1111111111000;          ///< power needed for claim last orb
    static const int maxLevel = 31;                         ///< max orb claimed
    int count = 0;                                          ///< how may orb claimed

    /**
     * @brief generate price list for claim orbs
     */
    static constexpr std::array<int64_t, maxLevel> priceList() {
        std::array<int64_t, maxLevel> list = { 0 };
        for ( int level = 1; level < maxLevel; level++ )
        { list[static_cast<size_t>( level )] = ( 1 << ( level % 3 ) ) * 250 * pow10( level / 3 ); }
        return list;
    }

    /**
     * @brief change claimed orb count
     * @param   aCount  new count of claimed orbs
     * @return ProductType::Power amount, needed for claim
     */
    int64_t claim( int aCount );

    /**
     * @brief Calculate how many power spended to current orbs
     * @return how many power spended to orbs
     */
    int64_t value() const;
};

/**
 * @brief convert double number to string with suffix K, M, G( 123 -> 0.1 K )
 * @param num converted number
 */
std::string doubleToUnits( double num );
